/*
 * Digital Audio Mobile - A multi track studio for Windows Mobile
 * Copyright (C) 2008 David Olsson
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <sstream>

#include "Composition.h"
#include "Segment.h"
#include "Track.h"

using namespace std;

Composition::Composition(const TCHAR *filename) throw(): m_filename(0), m_audioDir(0), m_bpm(120), m_sigBpm(4), m_sigBnt(4), m_trackCount(1), m_modified(false) {
    m_filename = _tcsdup(filename);
    int i;
    for(i = (_tcslen(filename) - 1); i > 0 && filename[i] != TEXT('.'); i--);
    m_audioDir = new TCHAR[_tcslen(filename) + _tcslen(TEXT("Audio\\")) + 1];
    _tcscpy(m_audioDir, filename);
    _tcscpy(m_audioDir + i + 1, TEXT("Audio\\"));
    CreateDirectory(m_audioDir, 0);

    read();
}

Composition::~Composition() {
    for(list<Track *>::iterator iter = m_tracks.begin(); iter != m_tracks.end(); iter++) {
        delete(*iter);
    }
    if(m_audioDir != 0) {
        delete[](m_audioDir);
    }
    if(m_filename != 0) {
        delete[](m_filename);
    }
}

MusicTime Composition::startTime() const throw() {
    MusicTime time;
    for(list<Track *>::const_iterator iter = m_tracks.begin(); iter != m_tracks.end(); iter++) {
        for(list<Segment *>::const_iterator siter = (*iter)->segments().begin(); siter != (*iter)->segments().end(); siter++) {
            if((*siter)->position() < time) {
                time = (*siter)->position();
            }
        }
    }

    return(time);
}

MusicTime Composition::endTime() const throw() {
    MusicTime time;
    for(list<Track *>::const_iterator iter = m_tracks.begin(); iter != m_tracks.end(); iter++) {
        for(list<Segment *>::const_iterator siter = (*iter)->segments().begin(); siter != (*iter)->segments().end(); siter++) {
            if(((*siter)->position() + (*siter)->length()) > time) {
                time = (*siter)->position() + (*siter)->length();
            }
        }
    }

    return(time);
}

unsigned int Composition::beatsPerMinute() const throw() {
    return(m_bpm);
}

void Composition::setBeatsPerMinute(unsigned int bpm) throw() {
    m_bpm = bpm;
}

unsigned int Composition::beatsPerMeasure() const throw() {
    return(m_sigBpm);
}

void Composition::setBeatsPerMeasure(unsigned int bpm) throw() {
    m_sigBpm = bpm;
    m_modified = true;
}

unsigned int Composition::beatNoteType() const throw() {
    return(m_sigBnt);
}

void Composition::setBeatNoteType(unsigned int bnt) throw() {
    m_sigBnt = bnt;
    m_modified = true;
}

Track *Composition::addTrack() throw() {
    TCHAR nameBuf[128];
    wsprintf(nameBuf, TEXT("Track %d"), m_trackCount++);
    Track *track = new Track(this, nameBuf);
    m_tracks.push_back(track);
    m_modified = true;
    return(track);
}

void Composition::removeTrack(Track *track) throw() {
    track->unlinkSegments();
    delete(track);
    m_tracks.remove(track);
    m_modified = true;
}

unsigned int Composition::numTracks() const throw() {
    return(m_tracks.size());
}

const list<Track *> &Composition::tracks() const throw() {
    return(m_tracks);
}

list<Track *> &Composition::tracks() throw() {
    return(m_tracks);
}

void Composition::superPositionTracks(AudioBuffer *buffer, const MusicTime &bufferStart) throw() {
    for(list<Track *>::iterator iter = m_tracks.begin(); iter != m_tracks.end(); iter++) {
        (*iter)->superPositionSegments(buffer, bufferStart);
    }
}

bool Composition::isModified() const throw() {
    return(m_modified);
}

void Composition::setModified() throw() {
    m_modified = true;
}

void Composition::createAudioFilename(TCHAR *filename) const throw() {
    GetTempFileName(m_audioDir, TEXT("dam"), 0, filename);
    DeleteFile(filename);
    _tcscpy(filename + _tcslen(filename) - 4, TEXT(".wav"));
}

bool Composition::save() throw() {
    HANDLE hFile = CreateFile(m_filename, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if(hFile == INVALID_HANDLE_VALUE) {
        return(false);
    }

    stringstream ss;
    ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << endl << endl
       << "<damProject beatsPerMinute=\"" << m_bpm << "\" beatsPerMeasure=\"" << m_sigBpm << "\" beatNoteType=\"" << m_sigBnt << "\">" << endl
       << "    <tracks>" << endl;
    for(list<Track *>::const_iterator iter = m_tracks.begin(); iter != m_tracks.end(); iter++) {
        ss << "        <track name=\"";
        writeUtf8((*iter)->name(), ss);
        ss << "\" volume=\"" << int((*iter)->volume()) << "\" pan=\"" << int((*iter)->pan()) << "\" muted=\"" << ((*iter)->muted() ? "true" : "false") << "\">" << endl;
        for(list<Segment *>::const_iterator siter = (*iter)->segments().begin(); siter != (*iter)->segments().end(); siter++) {
            ss << "            <segment waveFile=\"";
            const TCHAR *waveFile = (*siter)->source();
            const TCHAR *ptr;
            for(ptr = (waveFile + _tcslen(waveFile) - 1); ptr != waveFile && *ptr != TEXT('\\'); ptr--);
            writeUtf8(ptr + 1, ss);
            ss << "\" position=\"" << (*siter)->position().toSamples(96000) << "\"/>" << endl;
        }
        ss << "        </track>" << endl;
    }
    ss << "    </tracks>" << endl
       << "</damProject>";

    DWORD written;
    WriteFile(hFile, ss.str().c_str(), ss.str().size() * sizeof(stringstream::char_type), &written, 0);
    CloseHandle(hFile);

    m_modified = false;
    return(true);
}

void Composition::read() throw() {
    m_hFile = CreateFile(m_filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if(m_hFile != INVALID_HANDLE_VALUE) {
        ISAXXMLReader *xmlReader;
        CoCreateInstance(CLSID_SAXXMLReader, 0, CLSCTX_INPROC_SERVER, IID_ISAXXMLReader, reinterpret_cast<void **>(&xmlReader));
        xmlReader->putContentHandler(this);

        VARIANT varStream;
        VariantInit(&varStream);
        varStream.vt = VT_UNKNOWN;
        varStream.punkVal = static_cast<ISequentialStream *>(this);
        xmlReader->parse(varStream);

        xmlReader->Release();
        CloseHandle(m_hFile);
    }
}

ULONG Composition::AddRef() {
    return(1);
}

ULONG Composition::Release() {
    return(1);
}

HRESULT Composition::QueryInterface(REFIID iid, void **ppvObject) {
    if(iid == IID_ISAXContentHandler) {
        *ppvObject = static_cast<ISAXContentHandler *>(this);
        return(S_OK);
    }
    else if(iid == IID_ISequentialStream) {
        *ppvObject = static_cast<ISequentialStream *>(this);
        return(S_OK);
    }
    else {
        return(E_NOINTERFACE);
    }
}

HRESULT Composition::putDocumentLocator(ISAXLocator *pLocator) {
    return(S_OK);
}

HRESULT Composition::startDocument() {
    return(S_OK);
}

HRESULT Composition::endDocument() {
    return(S_OK);
}

HRESULT Composition::startPrefixMapping(const wchar_t *pwchPrefix, int cchPrefix, const wchar_t *pwchUri, int cchUri) {
    return(S_OK);
}

HRESULT Composition::endPrefixMapping(const wchar_t *pwchPrefix, int cchPrefix) {
    return(S_OK);
}

HRESULT Composition::startElement(const wchar_t *pwchNamespaceUri, int cchNamespaceUri, const wchar_t *pwchLocalName, int cchLocalName, const wchar_t *pwchQName, int cchQName, ISAXAttributes *pAttributes) {
    const wchar_t *value;
    int len;

    if(wcscmp(pwchLocalName, L"damProject") == 0) {
        if(pAttributes->getValueFromName(L"", 0, L"beatsPerMinute", 14, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        m_bpm = wcstol(value, 0, 10);

        if(pAttributes->getValueFromName(L"", 0, L"beatsPerMeasure", 15, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        m_sigBpm = wcstol(value, 0, 10);

        if(pAttributes->getValueFromName(L"", 0, L"beatNoteType", 12, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        m_sigBnt = wcstol(value, 0, 10);
    }
    else if(wcscmp(pwchLocalName, L"track") == 0) {
        if(pAttributes->getValueFromName(L"", 0, L"name", 4, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        Track *track = new Track(this, value);

        if(pAttributes->getValueFromName(L"", 0, L"volume", 6, &value, &len) == S_OK) {
            track->setVolume(wcstol(value, 0, 10));
        }
        
        if(pAttributes->getValueFromName(L"", 0, L"pan", 3, &value, &len) == S_OK) {
            track->setPan(wcstol(value, 0, 10));
        }

        if(pAttributes->getValueFromName(L"", 0, L"muted", 5, &value, &len) == S_OK) {
            if(wcscmp(value, L"true") == 0) {
                track->toggleMute();
            }
        }

        m_tracks.push_back(track);
    }
    else if(wcscmp(pwchLocalName, L"segment") == 0) {
        Track *track = m_tracks.back();

        if(pAttributes->getValueFromName(L"", 0, L"position", 8, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        MusicTime pos(wcstol(value, 0, 10), 96000);
        Segment *seg = new Segment(pos);

        if(pAttributes->getValueFromName(L"", 0, L"waveFile", 8, &value, &len) != S_OK) {
            return(E_FAIL);
        }
        TCHAR filename[MAX_PATH];
        _tcscpy(filename, m_audioDir);
        _tcscat(filename, value);
        seg->setSource(filename);

        track->addSegment(seg);
    }

    return(S_OK);
}

HRESULT Composition::endElement(const wchar_t *pwchNamespaceUri, int cchNamespaceUri, const wchar_t __RPC_FAR *pwchLocalName, int cchLocalName, const wchar_t *pwchQName, int cchQName) {
    return(S_OK);
}

HRESULT Composition::characters(const wchar_t *pwchChars, int cchChars) {
    return(S_OK);
}

HRESULT Composition::ignorableWhitespace(const wchar_t *pwchChars, int cchChars) {
    return(S_OK);
}

HRESULT Composition::processingInstruction(const wchar_t *pwchTarget, int cchTarget, const wchar_t *pwchData, int cchData) {
    return(S_OK);
}

HRESULT Composition::skippedEntity(const wchar_t *pwchName, int cchName) {
    return(S_OK);
}

HRESULT Composition::Read(void *pv, ULONG cb, ULONG *pcbRead) {
    ReadFile(m_hFile, pv, cb, pcbRead, 0);
    if(*pcbRead != 0) {
        return(S_OK);
    }
    else {
        return(S_FALSE);
    }
}

HRESULT Composition::Write(const void *pv, ULONG cb, ULONG *pcbWritten) {
    ASSERT(!"Shouldn't be here!");
    return(S_OK);
}

void Composition::writeUtf8(const TCHAR *str, stringstream &ss) throw() {
    for(unsigned int i = 0; str[i] != TEXT('\0'); i++) {
        if(str[i] < 0x80) {
            ss << char(str[i]);
        }
        else if(str[i] < 0x800) {
            ss << (0xc0 | char(str[i] >> 6));
            ss << (0x80 | char(str[i] & 0x1f));
        }
        else if(str[i] < 0x10000) {
        }
        else {
        }
    }
}
